/**
 *                            License
 * THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS  
 * CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). 
 * THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW.  
 * ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR  
 * COPYRIGHT LAW IS PROHIBITED.
 * 
 * BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND  
 * AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE  
 * MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED 
 * HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
 * 
 */
package l1j.server.server;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.logging.Level;
import java.util.logging.Logger;

import l1j.server.Config;
import l1j.server.server.Opcodes;
import l1j.server.server.clientpackets.C_ServerVersion;
import l1j.server.server.datatables.CharBuffTable;
import l1j.server.server.model.L1DragonSlayer;
import l1j.server.server.model.Getback;
import l1j.server.server.model.L1World;
import l1j.server.server.model.Instance.L1DollInstance;
import l1j.server.server.model.Instance.L1FollowerInstance;
import l1j.server.server.model.Instance.L1NpcInstance;
import l1j.server.server.model.Instance.L1PcInstance;
import l1j.server.server.model.Instance.L1PetInstance;
import l1j.server.server.model.Instance.L1SummonInstance;
import l1j.server.server.model.game.L1PolyRace;
import l1j.server.server.model.trade.L1Trade;
import l1j.server.server.serverpackets.S_PacketBox;
import l1j.server.server.serverpackets.S_SummonPack;
import l1j.server.server.serverpackets.ServerBasePacket;
import l1j.server.server.utils.ByteArrayUtil;
import l1j.server.server.utils.StreamUtil;

public class ClientThread implements Runnable, PacketOutput {

	private int _clientId;

	private boolean showpacket = false;

	private static Logger _log = Logger.getLogger(ClientThread.class.getName());

	private BufferedInputStream _in;

	private BufferedOutputStream _out;

	private Account _account = null;

	private L1PcInstance _activeChar;

	private String _ip;

	private String _hostname;

	private Socket _csocket;

	private IntervalChecker _intervalchecker = null;

	public IntervalChecker getIntervalChecker() {
		return _intervalchecker;
	}

	private Opcodes _opcode;

	public Opcodes getOpcode() {
		return _opcode;
	}

	private static final byte[] FIRST_PACKET = { (byte) 0x4c, (byte) 0xd4,
			(byte) 0x1e, (byte) 0x35, (byte) 0x74, (byte) 0x9f, (byte) 0xe8,
			(byte) 0x00, (byte) 0x05, (byte) 0xe8, (byte) 0x2b };
	
	// private static final byte[] FIRST_PACKET = { (byte) 0x36, (byte) 0x00,
    // (byte) 0x42, (byte) 0x1d, (byte) 0xd1, (byte) 0x7e, (byte) 0x0d,
    // (byte) 0x00, (byte) 0x3d, (byte) 0x75, (byte) 0x00 };

	public int getClientId() {
		return _clientId;
	}

	public void setClientId(int clientId) {
		_clientId = clientId;
	}

	public ClientThread(Socket socket) throws IOException {
		_csocket = socket;
		_ip = socket.getInetAddress().getHostAddress();
		if (Config.HOSTNAME_LOOKUPS) {
			_hostname = socket.getInetAddress().getHostName();
		} else {
			_hostname = _ip;
		}
		_in = new BufferedInputStream(socket.getInputStream());
		_out = new BufferedOutputStream(socket.getOutputStream());

	}

	public String getIp() {
		return _ip;
	}

	public String getHostname() {
		return _hostname;
	}

	private byte[] readPacket() throws Exception {
		try {
			int hiByte = _in.read();
			int loByte = _in.read();
			if ((loByte < 0) || (hiByte < 0)) {
				throw new RuntimeException();
			}

			final int dataLength = ((loByte << 8) + hiByte) - 2;
			if ((dataLength <= 0) || (dataLength > 65533)) {
				throw new RuntimeException();
			}

			byte data[] = new byte[dataLength];

			int readSize = 0;

			for (int i = 0; i != -1 && readSize < dataLength; readSize += i) {
				i = _in.read(data, readSize, dataLength - readSize);
			}

			if (readSize != dataLength) {
				_log.warning("Incomplete Packet is sent to the server, closing connection.");
				throw new RuntimeException();
			}

			return _cipher.decrypt(data);
		} catch (Exception e) {
			throw e;
		}
	}

	private Cipher _cipher;

	// private static final int FIRST_ID = 0xb4; // 252
	private static final int FIRST_ID = 30;

	@Override
	public void run() {

		String keyHax = "";
		int key = 0;
		byte Bogus = 0;

		try {
			keyHax = Integer
					.toHexString((int) (Math.random() * 2147483647) + 1);
			key = Integer.parseInt(keyHax, 16);

			Bogus = (byte) (FIRST_PACKET.length + 7);
			_out.write(Bogus & 0xFF);
			_out.write(Bogus >> 8 & 0xFF);
			_out.write(FIRST_ID);
			_out.write((byte) (key & 0xFF));
			_out.write((byte) (key >> 8 & 0xFF));
			_out.write((byte) (key >> 16 & 0xFF));
			_out.write((byte) (key >> 24 & 0xFF));
			_out.write(FIRST_PACKET);
			_out.flush();

		} catch (Throwable e) {
			_log.info("異常用戶端(" + _hostname + ") 連結到伺服器, 已中斷該連線。");
			closeConnection();
			return;
		}

		try {

			_cipher = new Cipher(key);
			byte[] clientPacket = null;
			try {
				clientPacket = readPacket();
			} catch (Exception e) {
				return;
			}
			int clientVersion = 0;
			clientVersion += clientPacket[1] & 0xff;
			clientVersion += (clientPacket[2] << 8) & 0xff00;
			clientVersion += (clientPacket[3] << 16) & 0xff0000;
			clientVersion += (clientPacket[4] << 24) & 0xff000000;
			if (clientVersion != Config.VERSION) {
				LoginController.getInstance().disconnectClient(_clientId);
				return;
			}
			_opcode = Opcodes.get();
			PacketSender packetSender = new PacketSender();
			GeneralThreadPool.getInstance().execute(packetSender);
			PacketReceiver receiver_slow = new PacketReceiver();
			GeneralThreadPool.getInstance().execute(receiver_slow);
			PacketReceiver receiver_fast = new PacketReceiver();
			GeneralThreadPool.getInstance().execute(receiver_fast);

			new C_ServerVersion(clientPacket, this);
			while (true) {
				byte data[] = null;
				try {
					data = readPacket();
				} catch (Exception e) {
					break;
				}
				int opcode = data[0] & 0xFF;
				if (showpacket) {
					System.out.println("[client]opcode =" + opcode + "\n"
							+ new ByteArrayUtil(data).dumpToString());
				}

				if (opcode == _opcode.C_OPCODE_MOVECHAR
						|| opcode == _opcode.C_OPCODE_ATTACK
						|| opcode == _opcode.C_OPCODE_ARROWATTACK
						|| opcode == _opcode.C_OPCODE_USESKILL) {
					// 為了確保即時的移動，將移動的封包獨立出來處理
					receiver_fast.offer(data);
				} else {
					// 處理其他數據的傳遞
					receiver_slow.offer(data);
				}
			}
		} catch (Throwable e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		}
		LoginController.getInstance().disconnectClient(_clientId);
		return;
	}

	public void closeConnection() {
		StreamUtil.close(_out, _in);
		if (_csocket != null) {
			try {
				_csocket.close();
				_csocket = null;
			} catch (IOException e) {

			}
		}
	}

	@Override
	public void sendPacket(ServerBasePacket packet) {
		_SendQueue.offer(packet.setOpcodes(_opcode));
	}

	public void setActiveChar(L1PcInstance pc) {
		_activeChar = pc;
		if (_intervalchecker == null) {
			_intervalchecker = new IntervalChecker(pc);
		}
		if (pc == null) {
			_intervalchecker = null;
		}
	}

	public L1PcInstance getActiveChar() {
		return _activeChar;
	}

	public void setAccount(Account account) {
		_account = account;
	}

	public Account getAccount() {
		return _account;
	}

	public String getAccountName() {
		if (_account == null) {
			return null;
		}
		return _account.getName();
	}

	public void quitGame() {
		L1PcInstance pc = _activeChar;
		// 如果死掉回到城中，設定飽食度
		if (pc.isDead()) {
			try {
				Thread.sleep(2000);// 暫停該執行續，優先權讓給expmonitor
				int[] loc = Getback.GetBack_Location(pc, true);
				pc.setX(loc[0]);
				pc.setY(loc[1]);
				pc.setMap((short) loc[2]);
				pc.setCurrentHp(pc.getLevel());
				pc.set_food(40);
			} catch (InterruptedException ie) {
				ie.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		// 終止交易
		int tradeId = pc.getTradeID();
		if (tradeId != 0) { // トレード中
			L1Trade.getInstance().stopTrade(tradeId);
		}

		// 終止決鬥
		if (pc.getFightId() != 0) {
			L1PcInstance fightPc = (L1PcInstance) L1World.getInstance()
					.findObject(pc.getFightId());
			pc.setFightId(0);
			if (fightPc != null) {
				fightPc.setFightId(0);
				fightPc.sendPackets(new S_PacketBox(S_PacketBox.MSG_DUEL, 0, 0));
			}
		}

		// 離開組隊
		if (pc.isInParty()) { // 如果有組隊
			pc.getParty().leaveMember(pc);
		}

		// TODO: 離開聊天組隊(?)
		if (pc.isInChatParty()) { // 如果在聊天組隊中(?)
			pc.getChatParty().leaveMember(pc);
		}

		// 移除世界地圖上的寵物
		// 變更召喚怪物的名稱
		for (L1NpcInstance petNpc : pc.getPetList().values()) {
			if (petNpc instanceof L1PetInstance) {
				L1PetInstance pet = (L1PetInstance) petNpc;
				// 停止飽食度計時
				pet.stopFoodTimer(pet);
				pet.dropItem();
				pc.getPetList().remove(pet.getId());
				pet.deleteMe();
			} else if (petNpc instanceof L1SummonInstance) {
				L1SummonInstance summon = (L1SummonInstance) petNpc;
				for (L1PcInstance visiblePc : L1World.getInstance()
						.getVisiblePlayer(summon)) {
					visiblePc.sendPackets(new S_SummonPack(summon, visiblePc,
							false));
				}
			}
		}

		// 移除世界地圖上的魔法娃娃
		for (L1DollInstance doll : pc.getDollList().values())
			doll.deleteDoll();

		// 重新建立跟隨者
		for (L1FollowerInstance follower : pc.getFollowerList().values()) {
			follower.setParalyzed(true);
			follower.spawn(follower.getNpcTemplate().get_npcId(),
					follower.getX(), follower.getY(), follower.getHeading(),
					follower.getMapId());
			follower.deleteMe();
		}

		// 刪除屠龍副本此玩家紀錄
		if (pc.getPortalNumber() != -1) {
			L1DragonSlayer.getInstance().removePlayer(pc, pc.getPortalNumber());
		}

		// 儲存魔法狀態
		CharBuffTable.DeleteBuff(pc);
		CharBuffTable.SaveBuff(pc);
		pc.clearSkillEffectTimer();
		L1PolyRace.getInstance().checkLeaveGame(pc);

		// 停止玩家的偵測
		pc.stopEtcMonitor();

		try {
			pc.save();
			pc.saveInventory();
		} catch (Exception e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		}

		synchronized (pc) {
			// 從線上中登出角色
			pc.logout();
		}

	}

	// 自動存檔器 預設每十分鐘自動儲存
	private ScheduledFuture<?> _autoSaver = null;

	private static final int AUTO_SAVE_INTERVAL = 10 * 60 * 1000;

	public void startAutoSaver() {
		_autoSaver = GeneralThreadPool.getInstance().scheduleAtFixedRate(
				new AutoSave(), AUTO_SAVE_INTERVAL, AUTO_SAVE_INTERVAL);
	}

	public void stopAutoSaver() {
		if (_autoSaver != null) {
			_autoSaver.cancel(false);
		}
	}

	class AutoSave implements Runnable {
		public void run() {
			try {
				_activeChar.save();
				_activeChar.saveInventory();
			} catch (Exception e) {
				_log.warning("Client autosave failure.");
			}
		}
	}

	private class PacketReceiver implements Runnable {
		private final Queue<byte[]> _queue;
		private PacketHandler _handler;

		PacketReceiver() {
			_queue = new ConcurrentLinkedQueue<byte[]>();
			_handler = new PacketHandler(ClientThread.this);
		}

		public void offer(byte[] data) {
			_queue.offer(data);
		}

		public void run() {
			while (_csocket != null) {
				try {
					byte[] data = _queue.poll();
					if (data != null) {
						_handler.handlePacket(data);
					} else {
						Thread.sleep(10);
					}
				} catch (Exception e) {
					closeConnection();
					break;
				}
			}
		}
	}

	private Queue<ServerBasePacket> _SendQueue = new ConcurrentLinkedQueue<ServerBasePacket>();

	private class PacketSender implements Runnable {

		public PacketSender() {
		}

		public void run() {
			while (_csocket != null) {
				try {
					ServerBasePacket packet = _SendQueue.poll();
					if (packet == null) {
						Thread.sleep(10);
					} else {
						byte content[];
                        try {
                                content = packet.getContent();
                        } catch (Exception e) {
                                continue;
                        }
						byte data[] = Arrays.copyOf(content, content.length);
						if (showpacket) {
							int op = (data[0] & 0xff);
							System.out.println("-----------------------------");
							System.out.println("  [Server] opcode = " + (op)
									+ " " + packet.getType() + "  length="
									+ (data.length));
							System.out.println(new ByteArrayUtil(data)
									.dumpToString());
						}
						_cipher.encrypt(data);
						int length = data.length + 2;
						_out.write(length & 0xff);
						_out.write(length >> 8 & 0xff);
						_out.write(data);
						_out.flush();
					}
				} catch (Exception e) {
					closeConnection();
					break;
				}
			}
		}
	}
}
